package IC.asm;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import IC.asm.operands.Label;
import IC.asm.operands.MachineReg;
import IC.asm.operands.Operand;
import IC.asm.operands.MachineReg.Regs;


public class Instruction extends ASMEntry {

	private Operand source, dest;

	/** instruction kind */
	private Ops op;
	
	/** what we need to save and restore **/
	private List<Regs> sideEffects;

	/** enum of possible operations of the instruction */
	public enum Ops {
		MOV,

		ADD,
		SUB,
		IDIV,
		IMUL,
		NEG,
		
		CDQ,
		SAL,
		SAR,
	
		PUSH,
		POP,
		
		AND,
		OR,
		XOR,
		NOT,

		CALL,
		JMP,
		
		JE,
		JNE,
		JG,
		JL,
		JGE,
		JLE,
		
		CMP,
		
		RET;
		
		public String toString() {
			String str = super.toString();
			return str.toLowerCase();
		}
		
		public static EnumSet<Ops> UnaryDestOps =
			EnumSet.of(Ops.POP, Ops.NEG, Ops.NOT, Ops.IDIV);
		
		public static EnumSet<Ops> DstLvalOps = 
			EnumSet.of(Ops.MOV,	Ops.ADD, Ops.SUB, Ops.IDIV, Ops.IMUL,
					Ops.NEG, Ops.SAL, Ops.SAR,
					Ops.POP,Ops.AND, Ops.OR, Ops.XOR, Ops.NOT);
		
		public static EnumSet<Ops> DstRvalOps = 
			EnumSet.of(Ops.ADD, Ops.SUB, Ops.IDIV, Ops.IMUL,
					Ops.NEG, Ops.SAL, Ops.SAR,
					Ops.AND, Ops.OR, Ops.XOR, Ops.NOT,
					Ops.CMP);

		public static EnumSet<Ops> ControlFlowOps =
			EnumSet.of(Ops.CALL, Ops.JMP, Ops.JE, Ops.JNE,
					Ops.JG, Ops.JL, Ops.JGE, Ops.JLE);
		
		public static EnumSet<Ops> NeedSizeOps =
			EnumSet.of(Ops.MOV,	Ops.ADD, Ops.SUB, Ops.IDIV, Ops.IMUL,
					Ops.NEG,
					Ops.SAL, Ops.SAR,
					Ops.PUSH, Ops.POP, Ops.CMP,
					Ops.AND, Ops.OR, Ops.XOR, Ops.NOT);
	}
	
	/** available jump kinds (conditions) */
	public enum JumpCondition {
		UNCONDITIONAL,
		TRUE,
		FALSE,
		GREATER,
		GREATEREQUAL,
		LESSTHAN,
		LESSTHANEQUAL
	}

	/**
	 * @param source the source operand
	 * @param dest the destination operand
	 * @param op instrcution kind
	 */
	public Instruction(Ops operator, Operand source, Operand dest,
					Regs ... sideEffects) {
		this.op = operator;
		this.source = source;
		this.dest = dest;
		this.sideEffects = new ArrayList<Regs>();
		switch (op) {
		case CALL:
			this.sideEffects.add(Regs.eax);
			break;
		case IDIV:
			this.sideEffects.add(Regs.eax);
			this.sideEffects.add(Regs.edx);
			break;
		}
		for (int i=0;i<sideEffects.length;i++) {
			this.sideEffects.add(sideEffects[i]);
		}
	}

	//Overload
	public Instruction(Ops operator, Regs source, Operand dest) {
		this(operator, new MachineReg(source), dest);
	}

	//Overload
	public Instruction(Ops operator, Operand source, Regs dest) {
		this(operator, source, new MachineReg(dest));
	}

	//Overload
	public Instruction(Ops operator, Regs source, Regs dest) {
		this(operator, new MachineReg(source), new MachineReg(dest));
	}
	
	/**
	 * @param source the source operand
	 * @param dest the destination operand
	 * @param op instrcution kind
	 */
	public Instruction(Ops operator, Operand operand) {
		this(operator, (Operand)null, (Operand)null);
		if (Ops.UnaryDestOps.contains(operator)) {
			this.dest = operand;
		}
		else {
			this.source = operand;
		}
	}

	//Overload
	public Instruction(Ops operator, Regs reg) {
		this(operator, new MachineReg(reg));
	}
	
	/**
	 * create no-operands instruction
	 * @param operator
	 */
	public Instruction(Ops operator) {
		this(operator, (Operand)null, (Operand)null);
	}
	
	public Instruction(Label lbl, JumpCondition cond) {
		switch(cond) {
		case UNCONDITIONAL:
			this.op = Ops.JMP;
			break;
		case TRUE:
			this.op = Ops.JE;
			break;
		case FALSE:
			this.op = Ops.JNE;
			break;
		case GREATER:
			this.op = Ops.JG;
			break;
		case GREATEREQUAL:
			this.op = Ops.JGE;
			break;
		case LESSTHAN:
			this.op = Ops.JL;
			break;
		case LESSTHANEQUAL:
			this.op = Ops.JLE;
			break;
		}
		this.source = lbl;
		this.dest = null;
		this.sideEffects = new ArrayList<Regs>();
	}

	/**
	 * @return the op
	 */
	public Ops getOp() {
		return op;
	}

	/**
	 * @return the dest
	 */
	public Operand getDest() {
		return dest;
	}

	/**
	 * @return the source
	 */
	public Operand getSource() {
		return source;
	}

	public <D,U> U accept(Visitor<D,U> v, D context) {
		return v.visit(this,context);
	}

	/**
	 * @return the sideEffects
	 */
	public List<Regs> getSideEffects() {
		assert(sideEffects != null);
		return sideEffects;
	}
}
